/*
  (*) 2010-2014 Michael Ferguson <michaelferguson@acm.org>

    * This is a work of the United States Government and is not protected by
      copyright in the United States.

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

  femto/src/mod_femto/mod_femto.c
*/

/* 
**  mod_femto.c -- Apache sample femto module
**  [Autogenerated via ``apxs -n femto -g'']
**
**  To play with this sample module first compile it into a
**  DSO file and install it into Apache's modules directory 
**  by running:
**
**    $ apxs -c -i mod_femto.c
**
**  Then activate it in Apache's httpd.conf file for instance
**  for the URL /femto in as follows:
**
**    #   httpd.conf
**    LoadModule femto_module modules/mod_femto.so
**    <Location /femto>
**    SetHandler femto
**    </Location>
**
or
<IfModule femto_module>
    AddHandler femto .femto
</IfModule>

**  Then after restarting Apache via
**
**    $ apachectl restart
**
**  you immediately can request the URL /femto and watch for the
**  output of this module. This can be achieved for instance via:
**
**    $ lynx -mime_header http://localhost/femto 
**
**  The output should be similar to the following one:
**
**    HTTP/1.1 200 OK
**    Date: Tue, 31 Mar 1998 14:42:22 GMT
**    Server: Apache/1.3.4 (Unix)
**    Connection: close
**    Content-Type: text/html
**  
**    The sample page from mod_femto.c


USAGE --
besides appropriately configuring Apache..
 - this module only responds to POST requests,
   where the query is in the POST body.
 - we respond with... any number of spaces (dopey but checks
   for end of connection) and the query response.


*/ 

#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
#include "http_log.h"
#include "ap_config.h"
#include "apr.h"
#include "apr_pools.h"
#include "apr_atomic.h"
#include "apr_strings.h"
#include <unistd.h>
#include <time.h>

// FEMTO includes.
#include "femto.h"

static apr_uint32_t femto_num_running;
femto_server_t femto_server;

// dbuf is the request text
static int femto_do_request(request_rec* r, char* dbuf)
{
  int i;
  int rc;
  femto_request_t *femto_request = NULL;
  int zero;
  struct timespec start;
  struct timespec now;

  r->content_type = "text/plain";      

  if (!r->header_only) {
    char* index_path = NULL;
    int index_path_len = 0;

    index_path = apr_pstrdup(r->pool, r->filename);
    index_path_len = strlen(index_path);

    // Make a new version of the filename without the /r.
    while( index_path_len >= 0 &&
           index_path[index_path_len-1] != '/' &&
           index_path[index_path_len-1] == 'r' ) {
      index_path[index_path_len-1] = '\0';
      index_path_len--;
    }

    //printf("FEMTO got request for %s: %s\n", index_path, dbuf);

    // Look to break out of.
    for( zero = 0; zero < 1; zero++ ) {
      // Start going!
      rc = femto_create_generic_request(&femto_request,
                                       &femto_server,
                                       index_path,
                                       dbuf);
      if( rc ) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
                      "FEMTO could not create request");
        return HTTP_INTERNAL_SERVER_ERROR;
      }

      rc = femto_begin_request(&femto_server, femto_request);
      if( rc ) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
                      "FEMTO could not begin request");
        return HTTP_INTERNAL_SERVER_ERROR;
      }


      start.tv_sec = 0;
      start.tv_nsec = 0;
      now.tv_sec = 0;
      now.tv_nsec = 0;

      for( i = 0; ! r->connection->aborted; i++ ) {
        int completed = 0;
        struct timeval tv;
        struct timespec ts;

        rc = gettimeofday(&tv, NULL);
        ts.tv_sec = tv.tv_sec;
        ts.tv_nsec = 1000 * tv.tv_usec;
        //rc = clock_gettime(CLOCK_REALTIME, &ts);
        if( rc ) {
          ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
                        "FEMTO could not gettime");
          return HTTP_INTERNAL_SERVER_ERROR;
        }

        ts.tv_sec += 2;

        rc = femto_timedwait_request(&femto_server, femto_request, &ts, &completed);
        if( rc ) {
          ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
                        "FEMTO could not wait for request");
          return HTTP_INTERNAL_SERVER_ERROR;
        }
        if( completed ) break;

        rc = gettimeofday(&tv, NULL);
        now.tv_sec = tv.tv_sec;
        now.tv_nsec = 1000 * tv.tv_usec;
        //rc = clock_gettime(CLOCK_REALTIME, &now);
        if( rc ) {
          ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
                        "FEMTO clock_gettime failed");
          return HTTP_INTERNAL_SERVER_ERROR;
        }

        if( now.tv_sec > start.tv_sec ) {
          // This is dopey.. but also the best way to check for
          // an aborted connection... just send a byte every second.
          ap_rputc(' ', r);
          ap_rflush(r);

          start = now;
        }
      }
    }

    if( ! r->connection->aborted ) {
      char* response;

      rc = femto_response_for_generic_request(femto_request, &femto_server, &response);
      if( rc ) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
                        "FEMTO response_for_generic_request failed");
        return HTTP_INTERNAL_SERVER_ERROR;
      }

      //printf("FEMTO sending response %s\n", response);
      ap_rputc('\n', r);
      ap_rputs(response, r);
      free(response);
    } else {
      rc = femto_cancel_request(&femto_server, femto_request);
      if( rc ) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r,
                      "FEMTO could not cancel request");
        return HTTP_INTERNAL_SERVER_ERROR;
      }
    }

    femto_destroy_request(femto_request);
  }

  return OK;
}


static int femto_get(request_rec* r)
{
  char* request = NULL;
  int rc;
  
  if( r->args ) {
    request = apr_pstrdup(r->pool, r->args);
    rc = ap_unescape_url_keep2f(request);
    if( rc != OK ) return rc;
    return femto_do_request(r, request);
  } else {
    return HTTP_INTERNAL_SERVER_ERROR;
  }

  return OK;
}

static int femto_post(request_rec* r)
{
  int bufbytes = 16*1024 - 1;
  char* dbuf = NULL;
  apr_size_t dbpos = 0;
  apr_bucket_brigade *bb;
  //apr_bucket *b;
  int seen_eos;
  conn_rec *c;
  apr_status_t rv;

  c = r->connection;


  // Read all of the put/post args into 'dbuf'
  // this code taken from mod_cgi.
  bb = apr_brigade_create(r->pool, c->bucket_alloc);
  seen_eos = 0;
  dbpos = 0;
  dbuf = apr_palloc(r->pool, bufbytes + 1);

  do {
      apr_bucket *bucket;

      rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
                          APR_BLOCK_READ, HUGE_STRING_LEN);

      if (rv != APR_SUCCESS) {
          ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                        "Error reading request entity data");
          return HTTP_INTERNAL_SERVER_ERROR;
      }

      for (bucket = APR_BRIGADE_FIRST(bb);
           bucket != APR_BRIGADE_SENTINEL(bb);
           bucket = APR_BUCKET_NEXT(bucket))
      {
          const char *data;
          apr_size_t len;

          if (APR_BUCKET_IS_EOS(bucket)) {
              seen_eos = 1;
              break;
          }

          /* We can't do much with this. */
          if (APR_BUCKET_IS_FLUSH(bucket)) {
              continue;
          }

          /* read */
          apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);

          {
              int cursize;

              if ((dbpos + len) > bufbytes) {
                  cursize = bufbytes - dbpos;
              } else {
                  cursize = len;
              }
              memcpy(dbuf + dbpos, data, cursize);
              dbpos += cursize;
          }
      }
      apr_brigade_cleanup(bb);
  }
  while (!seen_eos);

  dbuf[dbpos] = '\0';

  if(dbpos == 0) return HTTP_INTERNAL_SERVER_ERROR;

  return femto_do_request(r, dbuf);
}


/* The sample content handler */
static int femto_handler(request_rec *r)
{
  // Does the path start with /femto?
  if(memcmp(r->uri,"/femto", 6)) {
    return DECLINED;
  }

  // Does the path end with /r?
  {
    int len = strlen(r->uri);
    if( len > 2 && r->uri[len-2] == '/' && r->uri[len-1] == 'r' ) {
      // OK
    } else {
      return DECLINED;
    }
  }

  if( r->method_number != M_GET && r->method_number != M_POST ) {
    r->allowed |= (AP_METHOD_BIT << M_GET) | (AP_METHOD_BIT << M_POST);
    return DECLINED;
  }
  //fprintf(stdout, "FEMTO handler called with uri=%s filename=%s\n", r->uri, r->filename);

  //apr_pool_cleanup_register(r->pool, r, femto_cleanup_request, femto_cleanup_request);

  if( r->method_number == M_GET ) return femto_get(r);
  else return femto_post(r);
}

static apr_status_t femto_child_willexec(void* data)
{
  femto_num_running = 0;
  memset(&femto_server, 0, sizeof(femto_server));
  return APR_SUCCESS;
}

// Called when a child process is about to exec.
static apr_status_t femto_child_exit(void* data)
{
  apr_uint32_t nonzero = apr_atomic_dec32( &femto_num_running );
  //fprintf(stderr, "FEMTO stopping was %i\n", (int) nonzero);
  //fprintf(stdout, "FEMTO stopping was %i\n", (int) nonzero);
  if( ! nonzero ) {
    //fprintf(stdout, "FEMTO stopiing really\n");
    femto_stop_server(&femto_server);
  }

  return APR_SUCCESS;
}

static void femto_child_init(apr_pool_t *p, server_rec *s)
{
  int rc;
  apr_uint32_t was = apr_atomic_inc32( &femto_num_running );
  //fprintf(stderr, "FEMTO starting was %i\n", (int) was);
  if( was == 0 ) {
    rc = femto_start_server(&femto_server);
    if( rc ) {
      ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
                   "FEMTO could not start");
      return;
    }
    apr_pool_cleanup_register(p, s, femto_child_exit, femto_child_willexec);
  }
}

static void femto_register_hooks(apr_pool_t *p)
{
  ap_hook_child_init(femto_child_init, NULL, NULL, APR_HOOK_MIDDLE);
  // Need to have it be APR_HOOK_FIRST in order to handle GET
  // requests for our directory (and not get a dir listing).
  ap_hook_handler(femto_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA femto_module = {
    STANDARD20_MODULE_STUFF, 
    NULL,                  /* create per-dir    config structures */
    NULL,                  /* merge  per-dir    config structures */
    NULL,                  /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    NULL,                  /* table of config file commands       */
    femto_register_hooks   /* register hooks                      */
};

